Skip to main content

SAP S/4HANA Cloud

This guide walks you through the steps required to connect Docflo.ai to your SAP S/4HANA Public Cloud system, enabling seamless document integration and automated data posting.

πŸ“‹ Prerequisites:
  • SAP S/4HANA Cloud system with appropriate authorization
  • Basic authentication credentials for API access
  • Access to SAP API Business Hub or system administrator
  • Docflo.ai account with automation capabilities
  • Understanding of SAP OData services

πŸš€ Integration Steps​

Follow these steps to establish a connection between Docflo.ai and your SAP S/4HANA Cloud system:

Step 1: Create S/4HANA Cloud Connection​

  1. Navigate to the "Credentials" section in your Docflo.ai platform account settings
  2. Create a new SAP S/4HANA Cloud credentials with the following settings:
    • Host: Your SAP S/4HANA Cloud API base URL (e.g., https://your-tenant.s4hana.cloud.sap)
    • Username: Your SAP S/4HANA Cloud API user
    • Password: Your SAP S/4HANA Cloud API password
  3. Save the connection for use in automations

SAP Public Cloud Credentials

Step 2: Configure Document Fields​

  1. Go to your document type configuration in Docflo.ai
  2. Create or configure fields that will be used in SAP integration, such as:
    • Suppliers: Vendor information from SAP ODATA service API_BUSINESS_PARTNER (A_Supplier)
    • Company Codes: SAP company code mappings from SAP ODATA service API_COMPANYCODE_SRV (A_CompanyCode)
    • Cost Centers: Accounting allocation codes from SAP ODATA service api_cost_center (A_CostCenter_2)
    • GL Accounts: General ledger account numbers from SAP ODATA service API_GLACCOUNTINCHARTOFACCOUNTS_SRV (A_GLAccountInChartOfAccounts)
  3. Map field values to corresponding SAP field names
  4. Set field validation rules to ensure data quality
⚠️ Important Note: You can use customer fields and map anything that is available through ODATA endpoints.

Here is a sample configuration for company code selection list from API_COMPANYCODE_SRV.

SAP Supplier Select S4 public cloud

Step 3: Create Automation for Document Posting​

Create an automation in Docflo.ai using the following template code for supplier invoice creation:

interface InvoiceData {
invoiceId: string;
invoiceDate: string;
invoiceTotal: number;
invoiceSubTotal: number;
currency: string;
}

interface SAPInvoiceResponse {
SupplierInvoice: string;
status: string;
message?: string;
}

/**
* Handler that creates supplier invoices in SAP S/4HANA Cloud
*
* @param {object} event - Event data from the platform
* @param {object} secrets - Configuration secrets
* @param {object} manager - Document management interface
*/

import axios from 'axios';

export default async (event, secrets, manager) => {
const logs = [];

// Extract document data
const doc = event?.data?.doc;
const docId = doc?._id;
const invoiceData = doc?.docflo_results?.modelFields;

// Helper function to update document status
const updateDocumentStatus = async (status, sapInvoiceNumber = null, errorMsg = null) => {
if (!docId || !doc?.docflo_results) {
logs.push('Cannot update: Missing document data');
return false;
}

try {
const updatedDocfloResults = {
...doc.docflo_results,
modelFields: {
...doc.docflo_results.modelFields,
status: {
value: status,
type: 'string',
confidence: 1,
content: status
}
}
};

if (sapInvoiceNumber) {
// Here you will need to configure a field to save the invoice # back, like this
updatedDocfloResults.modelFields['SAP Invoice Number'] = {
value: sapInvoiceNumber,
type: 'string',
confidence: 1,
content: sapInvoiceNumber
};
}

if (errorMsg) {
updatedDocfloResults.modelFields.SAPError = {
value: errorMsg,
type: 'string',
confidence: 1,
content: errorMsg
};
}

const updateData = {
_id: docId,
status: status,
docflo_results: updatedDocfloResults
};

await manager.updateDocument(updateData);
logs.push('Document updated successfully');
return true;
} catch (updateError) {
logs.push(`Update failed: ${updateError.message}`);
return false;
}
};

try {
logs.push(`Starting SAP integration for document: ${docId}`);

if (!docId || !invoiceData) {
const errorMsg = 'Missing required document data';
logs.push(errorMsg);
return `ERROR: ${errorMsg}`;
}

// Extract invoice data with fallbacks
const invoiceId = invoiceData.InvoiceId?.value || 'INV001';
const invoiceDate = invoiceData.InvoiceDate?.value || new Date().toISOString().split('T')[0];
const invoiceTotal = invoiceData.InvoiceTotal?.value?.amount || 0;
const invoiceSubTotal = invoiceData.SubTotal?.value?.amount || 0;
const currency = invoiceData.InvoiceTotal?.value?.currencyCode || 'USD';

logs.push(`Processing invoice: ${invoiceId}, Total: ${invoiceTotal} ${currency}`);

// Format date for SAP
const formatDate = (dateStr) => {
if (dateStr && dateStr.length === 10) {
return dateStr + 'T00:00:00';
}
return new Date().toISOString().split('.')[0];
};

// Create authentication
const auth = Buffer.from(`${secrets.API_USER}:${secrets.API_PASSWORD}`).toString('base64');

// Build endpoint
const baseUrl = secrets.API_ENDPOINT;
const endpoint = baseUrl.replace(/\/$/, '') + '/sap/opu/odata/sap/API_SUPPLIERINVOICE_PROCESS_SRV/A_SupplierInvoice';

// Get CSRF Token (required by SAP)
let csrfToken = '';
let cookies = '';

logs.push('Fetching CSRF token...');

try {
const tokenResponse = await axios.get(endpoint, {
headers: {
'Authorization': `Basic ${auth}`,
'X-CSRF-Token': 'Fetch',
'Accept': 'application/json'
},
withCredentials: true
});

csrfToken = tokenResponse.headers['x-csrf-token'] || '';
cookies = tokenResponse.headers['set-cookie'] || '';

if (!csrfToken) {
throw new Error('Could not fetch CSRF token from SAP');
}

logs.push('CSRF token obtained successfully');

} catch (e) {
const errorMsg = `Failed to fetch CSRF token: ${e.message}`;
logs.push(errorMsg);
await updateDocumentStatus('ERROR', null, errorMsg);
return `ERROR: ${errorMsg}`;
}

// Build SAP request data
const sapData = {
CompanyCode: process.env.COMPANY_CODE || "1000",
DocumentDate: formatDate(invoiceDate),
PostingDate: formatDate(invoiceDate),
DocumentCurrency: currency,
InvoiceGrossAmount: invoiceTotal.toString(),
InvoicingParty: process.env.INVOICING_PARTY || "VENDOR001",
SupplierInvoiceIDByInvcgParty: invoiceId,
TaxDeterminationDate: formatDate(invoiceDate),
to_SupplierInvoiceTax: [{
TaxCode: process.env.TAX_CODE || "V1",
DocumentCurrency: currency
}],
to_SuplrInvcItemPurOrdRef: [{
PurchaseOrder: "PO001",
PurchaseOrderItem: "00010",
SupplierInvoiceItem: '1',
TaxCode: "V1",
SupplierInvoiceItemAmount: invoiceSubTotal.toString(),
DocumentCurrency: currency,
QuantityInPurchaseOrderUnit: "1",
PurchaseOrderQuantityUnit: "EA"
}]
};

// Make the request with CSRF token
const headers = {
'Authorization': `Basic ${auth}`,
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-CSRF-Token': csrfToken
};

if (cookies) {
headers['Cookie'] = cookies;
}

logs.push('Sending request to SAP...');

const response = await axios.post(endpoint, sapData, {
headers,
timeout: 30000,
withCredentials: true
});

if (response.status === 201) {
const result = response.data?.d as SAPInvoiceResponse;
const sapInvoiceNumber = result?.SupplierInvoice || 'CREATED';
logs.push(`Success! SAP Invoice created: ${sapInvoiceNumber}`);

await updateDocumentStatus("SUCCESS", sapInvoiceNumber);

return {
success: true,
message: 'Invoice created successfully in SAP S/4HANA Cloud',
invoiceNumber: sapInvoiceNumber,
data: result
};
}

const unexpectedStatus = `Unexpected status ${response.status}`;
logs.push(unexpectedStatus);
await updateDocumentStatus('ERROR', null, unexpectedStatus);
return {
success: false,
message: `ERROR: ${unexpectedStatus}`
};

} catch (error) {
let errorMessage = 'Request failed: ';

if (error.response) {
errorMessage += `Status ${error.response.status}. `;
if (error.response.data?.error?.message?.value) {
errorMessage += `SAP says: ${error.response.data.error.message.value}`;
} else if (error.response.data) {
errorMessage += `Response: ${JSON.stringify(error.response.data).substring(0, 500)}`;
}
} else {
errorMessage += error.message;
}

logs.push(`Error occurred: ${errorMessage}`);
await updateDocumentStatus('ERROR', null, errorMessage);

return {
success: false,
message: `ERROR: ${errorMessage}`,
error: errorMessage
};
}
};

πŸ”§ Configuration Parameters​

The automation requires the following environment variables and secrets:

Required Secrets (configured in Docflo.ai):​

  • API_USER: Your SAP S/4HANA Cloud API username
  • API_PASSWORD: Your SAP S/4HANA Cloud API password
  • API_ENDPOINT: Your SAP S/4HANA Cloud base API URL
⚠️ Important Notes:
  • SAP S/4HANA Cloud requires CSRF tokens for write operations
  • Basic authentication credentials must have appropriate API permissions
  • Test your automation thoroughly in a development environment first
  • Monitor API rate limits and implement proper error handling

πŸŽ‰ You're all set! Once you've completed these steps, your Docflo.ai platform will be able to automatically post document data to your SAP S/4HANA Cloud system and track the integration status.

πŸ“ž Support​

If you encounter any issues during the integration process, please contact the Docflo.ai support team or consult your SAP S/4HANA Cloud system administrator. Common troubleshooting steps include:

  • Verifying API credentials and permissions
  • Checking network connectivity and firewall rules
  • Reviewing SAP API documentation for field requirements
  • Testing with sample data before processing live documents